package ace.module.security.sdk.core.impl.api;

import ace.cmp.core.model.R;
import ace.cmp.i18n.api.MessageSourceAssertService;
import ace.cmp.security.oauth2.resource.server.core.service.Oauth2SecurityService;
import ace.module.account.api.IamAccountProfileApi;
import ace.module.security.api.AuthenticationApi;
import ace.module.security.api.constant.SecurityConstant;
import ace.module.security.api.model.dto.AccountProfileDto;
import ace.module.security.sdk.core.impl.converter.AccountProfileDtoConverter;
import java.util.Optional;
import java.util.function.Function;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
import org.springframework.stereotype.Component;

/**
 * @author caspar
 * @date 2023/10/26 14:45
 */
@Slf4j
@AllArgsConstructor
@Component
public class AuthenticationApiImpl implements AuthenticationApi {

  private static final ThreadLocal<Optional<AccountProfileDto>> accountProfileThreadLocal =
      new ThreadLocal<>();
  private final Oauth2SecurityService oAuth2SecurityService;
  private final IamAccountProfileApi iamAccountProfileApi;
  private final AccountProfileDtoConverter accountProfileDtoConverter;
  private final MessageSourceAssertService assertService;

  @Override
  public Long getCurrentAccountId() {
    Authentication authentication = oAuth2SecurityService.getAuthentication();
    Long id = (Long) Optional.ofNullable(authentication)
        .map(p -> (BearerTokenAuthentication) (p instanceof BearerTokenAuthentication ? p : null))
        .map(BearerTokenAuthentication::getPrincipal)
        .orElse(null);

    return id;
  }

  @Override
  public Long getAndCheckCurrentAccountId() {
    Long accountId = this.getCurrentAccountId();
    assertService.isNull(
        accountId,
        AuthenticationApi.class.getName() + ".error-authentication-fail-account-id-is-null",
        "登录认证已过期,请重新登录");
    return accountId;
  }

  @Override
  public AccountProfileDto getCurrentAccountProfile() {
    Optional<AccountProfileDto> accountProfileDtoOptional = accountProfileThreadLocal.get();
    if (accountProfileDtoOptional.isEmpty()) {
      accountProfileDtoOptional =
          Optional.ofNullable(this.getCurrentAccountId())
              .map(iamAccountProfileApi::getByAccountId)
              .map(R::getData)
              .map(this.accountProfileDtoConverter::convert);

      accountProfileThreadLocal.set(accountProfileDtoOptional);
    }
    return accountProfileDtoOptional.orElse(null);
  }

  @Override
  public String getCurrentIdOfClient() {
    return this.getAuthenticationAttributeToString(SecurityConstant.TOKEN_ATTRIBUTES_ID_OF_CLIENT_KEY);
  }

  @Override
  public Long getCurrentUpmsAppId() {
    return this.getAuthenticationAttributeToLong(SecurityConstant.TOKEN_ATTRIBUTES_UPMS_APP_ID_KEY);
  }

  @Override
  public Long getCurrentIamAppId() {
    return this.getAuthenticationAttributeToLong(SecurityConstant.TOKEN_ATTRIBUTES_IAM_APP_ID_KEY);
  }

  @Override
  public Boolean hasSuperAdministratorAuthority() {
    return this.oAuth2SecurityService.isSuperAdministrator();
  }

  @Override
  public Long getCurrentTenantId() {
    return this.getAuthenticationAttributeToLong(SecurityConstant.TOKEN_ATTRIBUTES_TENANT_ID_KEY);
  }

  @Override
  public Long getCurrentTenantAccountId() {
    return this.getAuthenticationAttributeToLong(SecurityConstant.TOKEN_ATTRIBUTES_TENANT_ACCOUNT_ID_KEY);
  }

  @Override
  public Long getCurrentMerchantId() {
    return this.getAuthenticationAttributeToLong(SecurityConstant.TOKEN_ATTRIBUTES_MERCHANT_ID_KEY);
  }

  @Override
  public Long getCurrentMerchantAccountId() {
    return this.getAuthenticationAttributeToLong(SecurityConstant.TOKEN_ATTRIBUTES_MERCHANT_ACCOUNT_ID_KEY);
  }

  @Override
  public Long getCurrentMerchantShopAccountId() {
    return this.getAuthenticationAttributeToLong(SecurityConstant.TOKEN_ATTRIBUTES_MERCHANT_SHOP_ACCOUNT_ID_KEY);
  }

  private Long getAuthenticationAttributeToLong(String attributeKey) {
    return this.getAuthenticationAttribute(attributeKey, Long::valueOf);
  }

  private String getAuthenticationAttributeToString(String attributeKey) {
    return this.getAuthenticationAttribute(attributeKey, String::valueOf);
  }

  private <T> T getAuthenticationAttribute(String attributeKey, Function<String, T> mapper) {
    Authentication authentication = oAuth2SecurityService.getAuthentication();
    T id = Optional.ofNullable(authentication)
        .map(p -> (BearerTokenAuthentication) (p instanceof BearerTokenAuthentication ? p : null))
        .map(BearerTokenAuthentication::getTokenAttributes)
        .map(attributes -> (String) attributes.get(attributeKey))
        .map(mapper)
        .orElse(null);

    return id;
  }
}
